module IPFS.Multiaddr(fromString,Multiaddr(..),encode,decode,fromBytes,nodeAddress) where

import Prelude

import Control.Monad.Error.Class (throwError)
import Data.Array (drop, fold, length, many, unsafeIndex)
import Data.ByteString as BS
import Data.Either (Either(..))
import Data.Serialize.Get (Get, getListOf, runGet)
import Data.Tuple (Tuple)
import Data.Tuple.Nested ((/\))
import IPFS.Multiaddr.IP (Port)
import IPFS.Multiaddr.Protocol as P
import Partial.Unsafe (unsafePartial)
import Text.Parsing.Parser (Parser, fail, runParser)

newtype Multiaddr = Multiaddr { protocols :: Array P.Protocol }

instance showMultiaddr :: Show Multiaddr where
   show (Multiaddr addr) = fold (map show addr.protocols)


parse::Parser String Multiaddr
parse = do
  ps <- many P.parse
  if (length ps > 0) then pure $ toAddr ps else fail "multiaddr protocol length = 0"
 where
   toAddr ls =  Multiaddr {protocols:ls}

fromString::String -> Either String Multiaddr
fromString str =  case (runParser str parse) of
                     Left  l -> Left $ show l
                     Right r -> Right r


fromBytes::BS.ByteString -> Either String Multiaddr
fromBytes  bs = runGet  decode bs

encode::Multiaddr -> BS.ByteString
encode (Multiaddr maddr) = fold $ map P.encode maddr.protocols
  
decode::Get Multiaddr
decode  = do
  plist <- getListOf P.decode
  when ((length plist) == 0) (throwError "Protocol length == 0")
  pure $ Multiaddr {protocols:plist}

dropBS::Int -> BS.ByteString -> BS.ByteString
dropBS len bs =  BS.pack $ drop len $ BS.unpack bs

nodeAddress::Multiaddr -> Either String (Tuple String Port)
nodeAddress (Multiaddr maddr) = do
   when ((length maddr.protocols) < 2) (throwError "multiaddr must have a valid format: /{ip4, ip6, dns4, dns6}/{address}/{tcp, udp}/{port}")
   let first = unsafePartial $ unsafeIndex maddr.protocols 0
   strAddr <- P.getStringAddress first
   let second = unsafePartial $ unsafeIndex maddr.protocols 1
   port <- P.getProtocolPort second
   pure $ strAddr /\ port
 where
   isAddrType (P.IP4 _) = true
   isAddrType (P.IP6 _) = true
   isAddrType  _     = false
                              
{-

function nodeAddress () {
  const codes = this.protoCodes()
  const names = this.protoNames()
  const parts = this.toString().split('/').slice(1)

  if (parts.length < 4) {
    throw new Error('multiaddr must have a valid format: "/{ip4, ip6, dns4, dns6}/{address}/{tcp, udp}/{port}".')
  } else if (codes[0] !== 4 && codes[0] !== 41 && codes[0] !== 54 && codes[0] !== 55) {
    throw new Error(`no protocol with name: "'${names[0]}'". Must have a valid family name: "{ip4, ip6, dns4, dns6}".`)
  } else if (parts[2] !== 'tcp' && parts[2] !== 'udp') {
    throw new Error(`no protocol with name: "'${names[1]}'". Must have a valid transport protocol: "{tcp, udp}".`)
  }

  return {
    family: (codes[0] === 41 || codes[0] === 55) ? 6 : 4,
    address: parts[1], // ip addr
    port: parts[3] // tcp or udp port
  }
}

-}
   